
(function(window){

/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * @fileoverview Shared javascript.
 * @author elsigh@google.com (Lindsey Simon)
 */

/**
 * Namespace for utility functions.
 * @type {Object}
 */
var Util = {};

/**
 * Adds CSS text to the DOM.
 * @param {string} cssText The css text to add.
 * @param {string} opt_id The id for the new stylesheet element.
 * @return {Element} cssNode the added css DOM node.
 */
Util.addCssText = function(cssText, opt_id) {
  var cssNode = document.createElement('style');
  cssNode.type = 'text/css';
  cssNode.id = opt_id ? opt_id : 'cssh-sheet-' + document.styleSheets.length;

  var headEl = document.getElementsByTagName('head')[0];
  headEl.appendChild(cssNode);

  // IE
  if (cssNode.styleSheet) {
	cssNode.styleSheet.cssText = cssText;
  // W3C
  } else {
	var cssText = document.createTextNode(cssText);
	cssNode.appendChild(cssText);
  }

  return cssNode;
};


/**
 * Preserve scope in timeouts.
 * @type {Object} scope
 * @type {Function} fn
 */
Util.curry = function(scope, fn) {
  var scope = scope || window;
  var args = [];
  for (var i = 2, len = arguments.length; i < len; ++i) {
	args.push(arguments[i]);
  };
  return function() {
	fn.apply(scope, args);
  };
};


/**
 * @return {boolean} true if IE, false otherwise.
 */
Util.isInternetExplorer = function() {
   return /msie/i.test(navigator.userAgent) &&
	   !/opera/i.test(navigator.userAgent);
};


/**
 * Read url param value from href.
 * @param {string} param The param to look for
 * @return {string} The value of the param or an empty string.
 */
Util.getParam = function(param) {
  param = param.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
  var regexString = '[\\?&]' + param + '=([^&#]*)';
  var regex = new RegExp(regexString);
  var results = regex.exec(window.location.href);
  if (results == null) {
	return '';
  } else {
	return results[1];
  }
};

/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @fileoverview Shared javascript for sending beacons and blocking so
 * that we know the beacon has completed.
 * @author elsigh@google.com (Lindsey Simon)
 */

/**
 * Adds a SCRIPT element to the DOM and monitors for existence of a completion
 * variable named BEACON_COMPLETE.
 * @param {string} uriParams URI params for the script element.
 * @param {Object} opt_doc Document object.
 * @param {string} opt_id DOM id for the script element.
 * @constructor
 */
var Beacon = function(uriParams, opt_doc, opt_id) {

  var id = opt_id || Beacon.DEFAULT_ID;

  /**
   * @type {Object}
   */
  this.doc_ = opt_doc || document;

  /**
   * @type {HTMLHeadElement}
   * @private
   */
  this.head_ = this.doc_.getElementsByTagName('head')[0];

  /**
   * @type {HTMLScriptElement}
   * @private
   */
  this.script_ = this.doc_.createElement('script');
  this.script_.type = 'text/javascript';
  this.script_.id = id;

  var src = Beacon.SERVER + '/beacon?' + uriParams + '&callback=1' +
	  Beacon.ADDTL_PARAMS;
  this.script_.src = src;

  /**
   * @type {Function}
   */
  this.checkCompleteCurry_ = Util.curry(this, this.checkComplete_);
};


/**
 * @type {number}
 * @private
 */
Beacon.completeInt_ = null;


/**
 * @type {number}
 * @private
 */
Beacon.COMPLETE_CHECK_SPEED = 50;


/**
 * @type {string}
 * @private
 */
Beacon.DEFAULT_ID = 'beacon';


/**
 * Can be used by implementations done to make requests to a
 * server on another domain.
 * @type {string}
 */
Beacon.SERVER = '';


/**
 * Can be used by implementations.
 * @type {string}
 */
Beacon.ADDTL_PARAMS = '';


/**
 * Sends a JSONP request and begins a timer to check that it has
 * completed.
 */
Beacon.prototype.send = function() {
  //console.log('sending with src: ', this.script_.src);
  this.head_.appendChild(this.script_);
  this.checkComplete_();
};


/**
 * Checks to see if the JSONP request has finished by looking for
 * a magic variable BEACON_COMPLETE.
 * @private
 */
Beacon.prototype.checkComplete_ = function() {
  if (typeof BEACON_COMPLETE != 'undefined') {
	window.clearTimeout(this.completeInt_);
	this.onComplete();
  } else {
	this.completeInt_ = window.setTimeout(
		this.checkCompleteCurry_, Beacon.COMPLETE_CHECK_SPEED);
  }
};


/**
 * To be made use of as desired by implementations.
 */
Beacon.prototype.onComplete = function() {};

/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
/**
 * @fileoverview The reflow timer attempts to time the speed of re-flowing a web
 * page. This means selector matching + rendering tree construction +
 * reflow and position/geometry recalculations.
 * Requires util.js and beacon.js
 * @author elsigh@google.com (Lindsey Simon)
 */

/**
 * The reflow timer class.
 * @param {boolean} opt_sendResultsToServer If true, send the results to our
 *     App Engine server for datastore-age.
 * @param {HTMLElement} opt_container The container element to manipulate.
 * @param {number} opt_passes The number of times to run the test for computing
 *     a median reflow time.
 * @constructor
 */
var ReflowTimer = function(sendResultsToServer, opt_container, opt_passes) {

  /**
   * @type {boolean}
   * @private
   */
  this.shouldSendResultsToServer_ = sendResultsToServer;

  /**
   * @type {number}
   * @private
   */
  this.passes_ = opt_passes || 3;

  /**
   * @type {HTMLDocumentElement}
   * @private
   */
  this.doc_ = window.document;

  /**
   * @type {HTMLBodyElement}
   * @private
   */
  this.body_ = this.doc_.body;

  /**
   * This is the element we tweak display none on to measure the reflow time.
   * Interesting to note here:
   * Setting document.body.style.display = 'none' in IE appears not to
   * actually cause the entire body to disappear, for instance buttons
   * remain laid out on the page but with their text removed. Bizarro.
   * @type {DocumentElement|BodyElement}
   * @private
   */
   this.containerEl_ = opt_container ? opt_container :
	   (Util.isInternetExplorer() ?
		this.doc_.documentElement : this.body_);

  /**
   * @type {Object}
   */
  this.containerStyle_ = this.containerEl_.style;

  /**
   * @type {Array}
   * @private
   */
  this.times_ = [];

  /**
   * @type {Array}
   * @private
   */
  this.reflowTimes_ = [];

  /**
   * @type {Array}
   * @private
   */
  this.medianReflowTimes_ = [];

  /**
   * @type {Array}
   * @private
   */
  this.normalizedTimes_ = [];

  /**
   * @type {Function}
   */
  this.testCurry_ = Util.curry(this, this.test_);

  /**
   * @type {Function}
   */
  this.testContinueCurry_ = Util.curry(this, this.testContinue_);

  /**
   * Start with the first test.
   * @type {number}
   */
  this.currentTestIndex_ = 0;

  /**
   * @type {Object}
   */
  this.results = {};

  /**
   * @type {Array.<string>}
   */
  this.resultsParams = [];
};

/**
 * Used for the bookmarklet's link back to the homepage.
 * @type {string}
 */
ReflowTimer.SERVER = 'ua-profiler.appspot.com';

/**
 * @type {number}
 */
ReflowTimer.REFLOW_REST_TIME = 250;


/**
 * @type {string}
 */
ReflowTimer.TEST_BODY_ATTR = 'bogusattribute';


/**
 * @type {string}
 */
ReflowTimer.TEST_BODY_ATTR_VAL = 'bogusvalue';


/**
 * @type {string}
 */
ReflowTimer.TEST_CLASS_NAME = 'rt-test-classname';


/**
 * @type {string}
 */
ReflowTimer.TEST_MULTIPLE_REFLOW_CSSTEXT = 'font-size: 20px !important; ' +
	'line-height: 10px !important;' +
	'padding-left: 10px !important;' +
	'margin-top: 7px; !important';


/**
 * @type {string}
 */
ReflowTimer.TEST_PADDING_LEFT_LONGHAND_CSSTEXT = 'padding: 0 0 0 4px;';


/**
 * @type {string}
 */
ReflowTimer.TEST_PADDING_LEFT_SHORTHAND_CSSTEXT = 'padding-left: 4px;';


/**
 * These are the enabled tests.
 * @type {Array.<string>}
 */
ReflowTimer.prototype.tests = [
  'testDisplay',
  'testVisibility',
  'testNonMatchingClass',
  'testFourClassReflows',
  'testFourScriptReflows',
  'testTwoScriptReflows',
  'testPaddingPx',
  'testPaddingLeftPx',
  'testFontSizeEm',
  'testWidthPercent',
  'testBackground',
  'testOverflowHidden',
  'testGetOffsetHeight'
];


/**
 * And these are the rest of the ones we've tried.
 * @type {Array.<string>}
 */
ReflowTimer.prototype.unusedTests = [
  'testSelectorMatchTime', // only works in FF this way
  'testLineHeight', // Same as testFontSize
  'testWidthPx', // Same as testWidthEm
  'testFontSizePx', // Same as testFontSizeEm
  'testOverflowVisible', // Seems uninteresting
  'testMarginPx', // Same as testPaddingPx
  'testPaddingLeftLonghandScript',
  'testPaddingLeftLonghandClass',
  'testPaddingLeftShorthandClass'
];


/**
 * Normalize values when sent to the server based on the following
 * test's score, using it as a golden test of 1x reflow.
 * @type {text|boolean}
 * @deprecated
 */
ReflowTimer.prototype.normalizeTimesToTest = false;


/**
 * @type {string}
 * @private
 */
ReflowTimer.prototype.currentTestName_ = null;


/**
 * @type {boolean}
 * @private
 */
ReflowTimer.prototype.recordPaintEvents_ = false;


/**
 * @type {Array}
 * @private
 */
ReflowTimer.prototype.paintEvents_ = [];


/**
 * @type {Array|string}
 * @private
 */
ReflowTimer.prototype.originalValue_ = null;


/**
 * @type {boolean}
 * @private
 */
ReflowTimer.prototype.showFeedbackDuringTests_ = true;


/**
 * @type {HTMLElement}
 * @private
 */
ReflowTimer.prototype.testFeedbackEl_ = null;


/**
 * Calculates the median value rounded to the nearest int for an array of
 * numbers.
 * @type {Array.<number>} array The array of numbers.
 * @return {number}
 */
ReflowTimer.getMedian = function(array) {
  array.sort(function(a, b) {
	var result = a - b;
	return result == 0 ? 0 : result / Math.abs(result);
  });
  var halfLen = array.length / 2;
  var index = array.length == 2 ? 0 : Math.floor(halfLen);
  var median;
  if (array.length == 1) {
	median = array[0];
  }
  else if (index == halfLen || index == 0) {
	median = (array[index + 1] + array[index]) / 2;
  } else {
	median = array[index];
  }
  return median.toFixed(0);
};


/**
 * @param {Element} el
 * @param {string} style to get
 * @return {string} computedStyle
 */
ReflowTimer.prototype.getComputedStyle = function(el, style) {
  var computedStyle = null;
  if (this.doc_.defaultView && this.doc_.defaultView.getComputedStyle) {
	var computedStyleObj =
		this.doc_.defaultView.getComputedStyle(el, '');
	var computedStyle = computedStyleObj[style];
  }
  return computedStyle;
};


/******************************
 * CLASS METHODS
 ******************************/


/**
 * Flush any style, rendering tree construction, and layout operations/queues.
 * This was a recommendation by David Baron at Mozilla. Thanks David!
 */
ReflowTimer.prototype.flushRenderQueue_ = function() {
  var offsetHeight = this.body_.offsetHeight;
};

/**
 * Only flushes style computations.
 */
ReflowTimer.prototype.flushStyleComputation_ = function() {
  this.getComputedStyle(this.doc_.body, 'color');
};

/**
 * Public function to begin the test routine.
 */
ReflowTimer.prototype.run = function() {

  // Cresultslear these from prior runs.
  this.results = {};
  this.resultsParams = []

  // Gets rid of prior test elements in the DOM.
  var previousElementIds = ['rt-content', 'rt-results',
	  'rt-feedback', 'rt-css', Beacon.DEFAULT_ID];
  for (var i = 0, id; id = previousElementIds[i]; i++) {
	var el = document.getElementById(id);
	if (el) {
	  el.parentNode.removeChild(el);
	}
  }

  // Adds the CSS for our class name test.
  if (!document.getElementById(ReflowTimer.TEST_CLASS_NAME)) {
	var cssText = '.' + ReflowTimer.TEST_CLASS_NAME + ' { ' +
		ReflowTimer.TEST_MULTIPLE_REFLOW_CSSTEXT + ' }';
	Util.addCssText(cssText, ReflowTimer.TEST_CLASS_NAME);
  }

  // css longhand test needs this.
  if (!document.getElementById(ReflowTimer.TEST_CLASS_NAME +
		'-padding-left-longhand')) {
	var cssText = '.' + ReflowTimer.TEST_CLASS_NAME +
		'-padding-left-longhand  { ' +
		ReflowTimer.TEST_PADDING_LEFT_LONGHAND_CSSTEXT + ' }';
	Util.addCssText(cssText, ReflowTimer.TEST_CLASS_NAME +
		'-padding-left-longhand');
  }
  // css shorthand test needs this.
  if (!document.getElementById(ReflowTimer.TEST_CLASS_NAME +
		'-padding-left-shorthand')) {
	var cssText = '.' + ReflowTimer.TEST_CLASS_NAME +
		'-padding-left-shorthand  { ' +
		ReflowTimer.TEST_PADDING_LEFT_SHORTHAND_CSSTEXT + ' }';
	Util.addCssText(cssText, ReflowTimer.TEST_CLASS_NAME +
		'-padding-left-shorthand');
  }

  // If possible listen for and record paint events.
  if (window.addEventListener && this.recordPaintEvents_) {
	var thisInstance = this;
	window.addEventListener('MozAfterPaint', function(e) {
		thisInstance.logPaintEvents_(e); }, false);
  }

  // Set up an element to show what test we're on.
  if (this.showFeedbackDuringTests_) {
	this.testFeedbackEl_ = document.createElement('div');
	this.testFeedbackEl_.id = 'rt-feedback';
	var style = this.testFeedbackEl_.style;
	style.position = 'absolute';
	style.fontSize = '13px';
	style.textAlign = 'center';
	style.background = '#eee';
	style.border = '1px solid #333';
	if (style.setProperty) {
	  style.setProperty('-moz-border-radius-bottom', '4px', '');
	  style.setProperty('-webkit-border-radius-bottom', '4px', '');
	}
	style.borderTop = '0';
	style.padding = '4px 20px';
	style.top = '0px';
	style.left = '40%';
	style.width = '22em';
	style.color = '#333';
	style.zIndex = '999';

	this.testFeedbackEl_.innerHTML = 'Beginning the Reflow tests...';
	this.body_.appendChild(this.testFeedbackEl_);
  }

  this.testStart_();
};


/**
 * Starts the test routine.
 * @private
 */
ReflowTimer.prototype.testStart_ = function() {

  this.currentTestName_ = this.tests[this.currentTestIndex_];

  if (!this.reflowTimes_[this.currentTestName_]) {
	this.reflowTimes_[this.currentTestName_] = [];
  }

  // If there's a setup function for the current test, run that first.
  if (this[this.currentTestName_ + 'SetUp']) {
	this[this.currentTestName_ + 'SetUp']();
  }

  if (this.testFeedbackEl_) {
	var feedback = 'Test '  + (this.currentTestIndex_ + 1) + ' of ' +
		this.tests.length + ' - ' +
		this.currentTestName_.replace('test', '') +
		' - pass ' +
		(this.reflowTimes_[this.currentTestName_].length + 1) +
		' of ' + this.passes_;
	this.testFeedbackEl_.innerHTML = feedback;
  }

  // Let the browser rest a little bit between tests.
  window.setTimeout(this.testCurry_, ReflowTimer.REFLOW_REST_TIME);
};


/**
 * Test routine.
 * @private
 */
ReflowTimer.prototype.test_ = function() {

  // Ensure the render queue is clean before starting the timer.
  this.flushRenderQueue_();

  // For testing selector match time we'll want to call a different
  // "flush" function.
  this.flushBetween_ = this.currentTestName_ == 'testSelectorMatchTime' ?
	  this.flushStyleComputation_ : this.flushRenderQueue_;

  var time1 = new Date().getTime();

  // Run the current test.
  this[this.currentTestName_]();

  // Now flush the render queue again, ensuring that our last change
  // gets pushed all the way through.
  this.flushBetween_();

  var time2 = new Date().getTime();

  var reflowTime = time2 - time1;
  this.reflowTimes_[this.currentTestName_].push(reflowTime);

  window.setTimeout(this.testContinueCurry_, ReflowTimer.REFLOW_REST_TIME);
};


ReflowTimer.prototype.testContinue_ = function() {
  // If there's a teardown function for the current test, run that now.
  if (this[this.currentTestName_ + 'TearDown']) {
	this[this.currentTestName_ + 'TearDown']();
  }
  this.flushRenderQueue_();

  // Do another pass of this test?
  if (this.reflowTimes_[this.currentTestName_].length != this.passes_) {
	this.testStart_();

  // On to the next test.
  } else if (this.currentTestIndex_ != this.tests.length - 1) {
	this.currentTestIndex_ += 1;
	this.testStart_();

  // All done with everything.
  } else {
	this.testsComplete_();
  }
};


/**
 * Completes the test routine.
 * @private
 */
ReflowTimer.prototype.testsComplete_ = function() {

  // Cleans up paint event listener before possibly rendering anything.
  if (window.addEventListener && this.recordPaintEvents_) {
	var thisInstance = this;
	window.removeEventListener('MozAfterPaint', function(e) {
		thisInstance.logPaintEvents_(e);}, false);
  }

  // Get all the median test times.
  for (var i = 0, test; test = this.tests[i]; i++) {
	this.medianReflowTimes_[test] =
		ReflowTimer.getMedian(this.reflowTimes_[test]);
  }

  // Normalized times.
  if (this.normalizeTimesToTest) {
	var benchmark = this.medianReflowTimes_[this.normalizeTimesToTest];
	for (var i = 0, test; test = this.tests[i]; i++) {
	  var percentage = Math.round(
		  (this.medianReflowTimes_[test]/benchmark) * 100);
	  this.normalizedTimes_[test] = percentage;
	}
  }

  // Results.
  for (var i = 0, test; test = this.tests[i]; i++) {
	var result = this.normalizeTimesToTest ?
				 this.normalizedTimes_[test] :
				 this.medianReflowTimes_[test]
	this.resultsParams.push(test + '=' + result);
	this.results[test] = result;
  }

  // Inserts a result el into the doc for selenium-rc to block on.
  var el = document.createElement('input');
  el.type = 'hidden';
  el.id = 'rt-results';
  el.value = this.resultsParams.join(',');
  this.body_.appendChild(el, this.body_.firstChild);

  if (this.testFeedbackEl_) {
	this.testFeedbackEl_.innerHTML = 'All done with the reflow tests!';
  }

  // Send and or render on page.
  if (this.shouldSendResultsToServer_) {
	if (this.testFeedbackEl_) {
	  this.testFeedbackEl_.innerHTML +=
		  '<br>Sending your results to be saved...';
	}
	this.sendResultsToServer_();
  }

  this.onTestsComplete(this.results);
};


/**
 * Provides a function for subclasses/functional uses to implement.
 * @param {array.<string>} results The results array where test=result.
 */
ReflowTimer.prototype.onTestsComplete = function(results) {};


/******************************
 * Various Reflow Tests
 ******************************/

ReflowTimer.prototype.testDisplaySetUp = function() {
  this.containerStyle_.display = 'none';
};
ReflowTimer.prototype.testDisplay = function() {
  this.containerStyle_.display = '';
};


ReflowTimer.prototype.testVisibilitySetUp = function() {
  this.containerStyle_.visibility = 'hidden';
};
ReflowTimer.prototype.testVisibility = function() {
  this.containerStyle_.visibility = 'visible';
};


ReflowTimer.prototype.testOverflowHiddenSetUp = function() {
  this.originalValue_ = this.containerStyle_.overflow;
};
ReflowTimer.prototype.testOverflowHidden = function() {
  this.containerStyle_.overflow = 'hidden';
};
ReflowTimer.prototype.testOverflowHiddenTearDown = function() {
  this.containerStyle_.overflow = this.originalValue_;
};


ReflowTimer.prototype.testOverflowAutoSetUp = function() {
  this.originalValue_ = this.containerStyle_.overflow;
};
ReflowTimer.prototype.testOverflowAuto = function() {
  this.containerStyle_.overflow = 'auto';
};
ReflowTimer.prototype.testOverflowAutoTearDown = function() {
  this.containerStyle_.overflow = this.originalValue_;
};


ReflowTimer.prototype.testOverflowVisibleSetUp = function() {
  this.originalValue_ = this.containerStyle_.overflow;
};
ReflowTimer.prototype.testOverflowVisible = function() {
  this.containerStyle_.overflow = 'visible';
};
ReflowTimer.prototype.testOverflowVisibleTearDown = function() {
  this.containerStyle_.overflow = this.originalValue_;
};


ReflowTimer.prototype.testMarginPxSetUp = function() {
  this.originalValue_ = this.containerStyle_.margin;
};
ReflowTimer.prototype.testMarginPx = function() {
  this.containerStyle_.margin = '20px';
};
ReflowTimer.prototype.testMarginPxTearDown = function() {
  this.containerStyle_.margin = this.originalValue_;
};


ReflowTimer.prototype.testPaddingPxSetUp = function() {
  this.originalValue_ = this.containerStyle_.padding;
};
ReflowTimer.prototype.testPaddingPx = function() {
  this.containerStyle_.padding = '20px';
};
ReflowTimer.prototype.testPaddingPxTearDown = function() {
  this.containerStyle_.padding = this.originalValue_;
};


ReflowTimer.prototype.testPaddingEmSetUp = function() {
  this.originalValue_ = this.containerStyle_.padding;
};
ReflowTimer.prototype.testPaddingEm = function() {
  this.containerStyle_.padding = '4em';
};
ReflowTimer.prototype.testPaddingEmTearDown = function() {
  this.containerStyle_.padding = this.originalValue_;
};


ReflowTimer.prototype.testPaddingLeftPxSetUp = function() {
  this.originalValue_ = this.containerStyle_.paddingLeft;
};
ReflowTimer.prototype.testPaddingLeftPx = function() {
  this.containerStyle_.paddingLeft = '20px';
};
ReflowTimer.prototype.testPaddingLeftPxTearDown = function() {
  this.containerStyle_.paddingLeft = this.originalValue_;
};


ReflowTimer.prototype.testPaddingLeftLonghandScriptSetUp = function() {
  this.originalValue_ = this.containerStyle_.padding;
};
ReflowTimer.prototype.testPaddingLeftLonghandScript = function() {
  this.containerStyle_.padding = '0 0 0 20px';
};
ReflowTimer.prototype.testPaddingLeftLonghandScriptTearDown = function() {
  this.containerStyle_.padding = this.originalValue_;
};


ReflowTimer.prototype.testPaddingLeftLonghandClassSetUp = function() {
  this.originalValue_ = this.containerEl_.className;
};
ReflowTimer.prototype.testPaddingLeftLonghandClass = function() {
  this.containerEl_.className = ReflowTimer.TEST_CLASS_NAME +
	  '-padding-left-longhand';
};
ReflowTimer.prototype.testPaddingLeftLonghandClassTearDown = function() {
  this.containerEl_.className = this.originalValue_;
};


ReflowTimer.prototype.testPaddingLeftShorthandClassSetUp = function() {
  this.originalValue_ = this.containerEl_.className;
};
ReflowTimer.prototype.testPaddingLeftShorthandClass = function() {
  this.containerEl_.className = ReflowTimer.TEST_CLASS_NAME +
	  '-padding-left-shorthand';
};
ReflowTimer.prototype.testPaddingLeftShorthandClassTearDown = function() {
  this.containerEl_.className = this.originalValue_;
};


ReflowTimer.prototype.testWidthPxSetUp = function() {
  this.originalValue_ = this.containerStyle_.width;
};
ReflowTimer.prototype.testWidthPx = function() {
  this.containerStyle_.width = '500px';
};
ReflowTimer.prototype.testWidthPxTearDown = function() {
  this.containerStyle_.width = this.originalValue_;
};


ReflowTimer.prototype.testLineHeightSetUp = function() {
  this.originalValue_ = this.containerStyle_.lineHeight;
};
ReflowTimer.prototype.testLineHeight = function() {
  this.containerStyle_.lineHeight = '2';
};
ReflowTimer.prototype.testLineHeightTearDown = function() {
  this.containerStyle_.lineHeight = this.originalValue_;
};


ReflowTimer.prototype.testFontSizePxSetUp = function() {
  this.originalValue_ = this.containerStyle_.fontSize;
};
ReflowTimer.prototype.testFontSizePx = function() {
  this.containerStyle_.fontSize = '20px';
};
ReflowTimer.prototype.testFontSizePxTearDown = function() {
  this.containerStyle_.fontSize = this.originalValue_;
};


ReflowTimer.prototype.testFontSizeEmSetUp = function() {
  this.originalValue_ = this.containerStyle_.fontSize;
};
ReflowTimer.prototype.testFontSizeEm = function() {
  this.containerStyle_.fontSize = '2em';
};
ReflowTimer.prototype.testFontSizeEmTearDown = function() {
  this.containerStyle_.fontSize = this.originalValue_;
};


ReflowTimer.prototype.testWidthPercentSetUp = function() {
  this.originalValue_ = this.containerStyle_.width;
};
ReflowTimer.prototype.testWidthPercent = function() {
  this.containerStyle_.width = '50%';
};
ReflowTimer.prototype.testWidthPercentTearDown = function() {
  this.containerStyle_.width = this.originalValue_;
};


ReflowTimer.prototype.testNonMatchingClassSetUp = function() {
  this.originalValue_ = this.containerEl_.className;
};
ReflowTimer.prototype.testNonMatchingClass = function() {
  this.containerEl_.className = ReflowTimer.TEST_CLASS_NAME + '-nomatch';
};
ReflowTimer.prototype.testNonMatchingClassTearDown = function() {
  this.containerEl_.className = this.originalValue_;
};


ReflowTimer.prototype.testFourClassReflowsSetUp = function() {
  this.originalValue_ = this.containerEl_.className;
};
ReflowTimer.prototype.testFourClassReflows = function() {
  this.containerEl_.className = ReflowTimer.TEST_CLASS_NAME;
};
ReflowTimer.prototype.testFourClassReflowsTearDown = function() {
  this.containerEl_.className = this.originalValue_;
};


ReflowTimer.prototype.testFourScriptReflowsSetUp = function() {
  this.originalValue_ = [];
  this.originalValue_[0] = this.containerStyle_.fontSize;
  this.originalValue_[1] = this.containerStyle_.lineHeight;
  this.originalValue_[2] = this.containerStyle_.paddingLeft;
  this.originalValue_[3] = this.containerStyle_.marginTop;
};
ReflowTimer.prototype.testFourScriptReflows = function() {
  this.containerStyle_.fontSize = '20px';
  this.containerStyle_.lineHeight = '10px';
  this.containerStyle_.paddingLeft = '10px';
  this.containerStyle_.marginTop = '7px';
};
ReflowTimer.prototype.testFourScriptReflowsTearDown = function() {
  this.containerStyle_.fontSize = this.originalValue_[0];
  this.containerStyle_.lineHeight = this.originalValue_[1];
  this.containerStyle_.paddingLeft = this.originalValue_[2];
  this.containerStyle_.marginTop = this.originalValue_[3];
};


ReflowTimer.prototype.testTwoScriptReflowsSetUp = function() {
  this.originalValue_ = [];
  this.originalValue_[0] = this.containerStyle_.fontSize;
  this.originalValue_[1] = this.containerStyle_.paddingLeft;
};
ReflowTimer.prototype.testTwoScriptReflows = function() {
  this.containerStyle_.fontSize = '20px';
  this.containerStyle_.paddingLeft = '10px';
};
ReflowTimer.prototype.testTwoScriptReflowsTearDown = function() {
  this.containerStyle_.fontSize = this.originalValue_[0];
  this.containerStyle_.paddingLeft = this.originalValue_[1];
};


ReflowTimer.prototype.testBackgroundSetUp = function() {
  this.originalValue_ = this.containerStyle_.background;
};
ReflowTimer.prototype.testBackground = function() {
  this.containerStyle_.background = '#f0f0f0';
};
ReflowTimer.prototype.testBackgroundTearDown = function() {
  this.containerStyle_.background = this.originalValue_;
};


ReflowTimer.prototype.testGetOffsetHeight = function() {};


/**
 * Tests the time to perform selector matching.
 * This may be Mozilla specific based on how they optimize.
 * David explained that by making a rule that couldn't match ever but yet gets
 * activated by setting a body attribute, we can guage the time to perform
 * selector matching without reflow.
 * @private
 */
ReflowTimer.prototype.testSelectorMatchTimeSetUp = function() {
  if (!document.getElementById('rt-css-selectors')) {
	var cssText = 'body[' + ReflowTimer.TEST_BODY_ATTR + '=' +
		ReflowTimer.TEST_BODY_ATTR_VAL + '] html {color:pink}';
	Util.addCssText(cssText, 'rt-css-selectors');
  }
};
ReflowTimer.prototype.testSelectorMatchTime = function() {
  this.body_.setAttribute(ReflowTimer.TEST_BODY_ATTR,
	  ReflowTimer.TEST_BODY_ATTR_VAL);
};
ReflowTimer.prototype.testSelectorMatchTimeTearDown = function() {
  this.body_.setAttribute(ReflowTimer.TEST_BODY_ATTR, '');
};


/**
 * @see http://ejohn.org/blog/browser-paint-events/
 */
ReflowTimer.prototype.logPaintEvents_ = function(e) {
  var log = {
	  'time': new Date().getTime(),
	  'clientRects': e.clientRects,
	  'boundingClientRect': e.boundingClientRect};
  this.paintEvents_.push(log);
};


/**
 * Sends the reflow time to our server.
 * @private
 */
ReflowTimer.prototype.sendResultsToServer_ = function() {

  var uriParams = 'category=reflow' + '&results=' +
	  this.resultsParams.join(',');

  // Add on params to the beacon.
  var paramsEl = document.getElementById('rt-params');
  if (paramsEl) {
	uriParams += '&params=' + paramsEl.value;

  // store the entire window.location.href as a param
  } else {
	uriParams += '&params=' + encodeURI(window.location.href);
  }

  // Gets our csrf_token from the page.
  var csrfTokenEl = document.getElementById('csrf_token');
  if (csrfTokenEl) {
	var csrfToken = csrfTokenEl.value;
	uriParams += '&csrf_token=' + csrfToken;
  }

  /**
   * @type {Beacon}
   */
  this.beacon_ = new Beacon(uriParams);
  this.beacon_.onComplete = Util.curry(this, this.onBeaconComplete_);
  this.beacon_.send();
};


/**
 * Private beacon complete method, which fires the public onBeaconComplete
 * method.
 * @private
 */
ReflowTimer.prototype.onBeaconComplete_ = function() {
  if (this.testFeedbackEl_) {
	this.testFeedbackEl_.innerHTML = 'Your results were saved.';
  }
  this.onBeaconComplete();
};


/**
 * Provides a function for subclasses/functional uses to implement.
 */
ReflowTimer.prototype.onBeaconComplete = function() {};





	var radom = Math.floor(Math.random() * 62586831288).toString(36),
		location = window.location.href,
		SERVER = window.location.protocol + "//" + window.location.host,
		$ = function(id) { return typeof id == "string" ? document.getElementById(id) : id; },
		isIE = window.ActiveXObject,
		urlReg = /^(http[s]?:\/\/[-\w]+(\.\w[-\w]*)+(:\d+)?[\/]?(?:[^;\"'<>()\[\]{}\s\x7F-\xFF!.,?]*)(?:[!.,?]+[^;\"'<>()\[\]{}\s\x7F-\xFF!.,?]+)*)$/i,
		performanceTmpl = "<table>" +
					"<caption>Current performance test report" + 
					"<thead><tr><th>Original page parser time<th>Minified page parser time<th>Original network latency<th>Minified network latency<th>Original page reflow time<th>Minified page reflow time" +
					"<%= !(result.oReflowTime || result.mReflowTime) ? '<tfoot><tr><td calspan=\"6\"><a id=\"reflow\" href=\"#\">Do Reflow</a>' : '' %>" +
					"<tbody><tr><td><%= result.oTime %>MS<td><%= result.mTime %>MS<td><%= result.oLatency %>MS<td><%= result.mLatency %>MS<td><%= result.oReflowTime ? result.oReflowTime +'MS' : '' %>&nbsp;<td><%= result.mReflowTime ? result.mReflowTime +'MS' : '' %>&nbsp;"
					"</table>",
		dislayStyle = "#_sm_console_ *{margin:0;padding:0;text-align:left;}#_sm_console_ table{*border-collapse:collapse;border-spacing:0;font-size:12px;line-height:1.5;}#_sm_console_ caption,#_sm_console_ th{text-align:left;font-style:normal;}#_sm_console_ a,#_sm_console_ a:hover{margin-left:20px;color:#0000EE!important;;font-size:16px!important;}#_sm_console_ div{text-align:center;}#_sm_console_ img,#_sm_console_ a img{border:0;}#_sm_console_ li{list-style:none;}#_sm_console_{border:1px solid #345191;width:680px!important;margin:0 auto!important;font-family:'tahoma','arial','sans-serif'!important;font-size:16px!important;line-height:16px!important;color:#333!important;background:#fff!important;position:fixed!important;z-index:1000001!important;}#_sm_console_ ul{padding-bottom:0.8em;}#_sm_console_ p{margin:8px 5px 2px;border-bottom:1px solid #D5D5D5;font-weight:bold;text-indent:5px;cursor:pointer;}#_sm_console_ ul dl{margin:5px 10px;font-size:11px;}#_sm_console_ dt{float:left;width:110px;font-weight:bold;text-align:right;}#_sm_console_ dd{margin-left:120px;}#_sm_console_ table{width:100%;margin:5px 10px;}#_sm_console_none_border_{border:none;margin:5px 10px;padding-left:1em;}#_sm_console_hide_{position:absolute;right:0;color:#fff;background:#345191;padding: 0 1em;height:26px;line-height:26px;font-weight:bold;cursor:pointer;}#_sm_console_show_{font-family:'tahoma','arial','sans-serif'!important;font-size:16px!important;line-height:26px!important;top:0;color:#fff;background:#345191;padding: 0 1em;height:26px;font-weight:bold;position:fixed!important;z-index:1000002!important;cursor:pointer;display:none;}#s\\:m{}",
		displayTmpl = '<div id="_sm_console_hide_"><span>hide</span></div><div style="padding-top:5px;"><a href="<%= server %>/" title="Go to Home"><img src="<%= server %>/img/logo.png" width="202" height="27" alt="logo"></a></div><ul><li><p>Minified result</p><div><table><thead><tr><th>Original size<th>Minified size<th>Savings<th>Savings percent<tr><td><%= page.originalLength %>( <%= page.originalDeflatedLength %> gzipped )<td><%= page.minifiedLength %>( <%= page.minifiedDeflatedLength %> gzipped )<td><%= page.savings %>( <%= page.deflatedSavings %> gzipped )<td><%= page.minfiedPercent %>( <%= page.deflatedPercent %> gzipped )</table><div><div><a target="_blank" href="<%= server %>/display?type=source&view=o&key=<%= key %>">View source of original page</a><a target="_blank" href="<%= server %>/display?type=source&key=<%= key %>">View source of minified page</a></div><div><a href="<%= server %>/evaluate?key=<%= key %>">Evaluate page parser performance</a><a href="<%= server %>/evaluate?reflow=true&key=<%= key %>">Evaluate page parser performance with reflow time</a></div><div><a href="<%= server %>/performance/detail?key=<%= key %>">View performance results</a><a id="_sm_console_options_" href="#">Let HTML Minifier follow the page links</a></div></div></div><li><p>Request headers<dl><% for (var i=0 , item = page.requestHeader , len = item.length; i < len; i++) {print("<dt>", item[i].name, "<dd>" ,item[i].value);} %></dl><li><p>Response headers<dl><% for (var i=0 , item = page.responseHeader , len = item.length; i < len; i++) {print("<dt>", item[i].name, "<dd>" ,item[i].value);} %></dl><li><p>Minified optionns<ul id="_sm_console_none_border_"><li>Remove comments ( <%= options["remove-comments"] %> )<li>Remove comments from scripts and styles ( <%= options["remove-comments-from-cdata"] %> )<li>Remove CDATA sections from scripts and styles ( <%= options["remove-cdata-sections-from-cdata"] %> )<li>Collapse whitespace ( <%= options["collapse-whitespace"] %> )<li>Collapse boolean attributes ( <%= options["collapse-boolean-attributes"] %> )<li>Remove attribute quotes ( <%= options["remove-attribute-quotes"] %> )<li>Remove redundant attributes/values ( <%= options["remove-redundant-attributes"] %> )<li>Use short doctype ( <%= options["use-short-doctype"] %> )<li>Remove empty (or blank) attributes ( <%= options["remove-empty-attributes"] %> )<li>Remove optional tags ( <%= options["remove-optional-tags"] %> )<li>Remove URLSchmeme ( <%= options["remove-url-scheme"] %> )<li>Remove type="text/javascript" from script"s ( <%= options["remove-script-type-attributes"] %> )<li>Remove type="text/css" from style"s ( <%= options["remove-style-type-attributes"] %> )<li>Remove empty elements ( <%= options["remove-empty-elements"] %> )</ul></ul>',
		report = {},
		insertCssText = function(cssText, opt_id) {
			var cssNode = document.createElement('style');
			cssNode.type = 'text/css';
			cssNode.id = opt_id ? opt_id : 'cssh-sheet-' + document.styleSheets.length;
			var headEl = document.getElementsByTagName('head')[0] || document.documentElement;
			headEl.insertBefore(cssNode,headEl.lastChild);
			if (cssNode.styleSheet) {
			  cssNode.styleSheet.cssText = cssText;
			} else {
			  var cssText = document.createTextNode(cssText);
			  cssNode.appendChild(cssText);
			}
			return cssNode;
		},
		jsonp = function(url,callback){	
			var jsonp_ = "jsonp" + (new Date).getTime(), 
				doc = document,
				header =  doc.getElementsByTagName('head')[0] || doc.documentElement,
				script = doc.createElement('script');
			window[ jsonp_ ] = function(date){
				callback(date);
				window[ jsonp ] = undefined;
				try{ delete window[ jsonp ]; } catch(e){}
				header.removeChild( script );
			}
			url = /callback=/.test(url) ? url :
				url + (/\?/.test(url) ? "&" : "?" ) + "callback=" + jsonp_;
			script.src = url ;
			header.insertBefore( script, header.firstChild );
		},
		evantID = 0;
		eventCache = {};
		fixEvent = function(e){
			if ( !e.preventDefault ) {
				e.preventDefault = function(){
					e.returnValue = false;
				}
			}
			if (!e.stopPropagation ) {
				e.stopPropagation = function(){
					e.cancelBubble = true;
				}
			}
			return e;
		},
		addEvent = function(el, type, listener) {
			if (window.attachEvent) {
				el.attachEvent("on" + type, function() {
					listener.call(el,fixEvent(window.event));
				});
			} else {
				el.addEventListener(type, listener, false);
			}
		},
		userAgent = navigator.userAgent.toLowerCase(),
		browser = {
			/*for Firefox,Safari,Chrome,Opera,IE*/
			version: (/.+(?:ox|on|me|ra|ie)[\/: ]([\d.]+)/.exec(userAgent) || [0,'0'])[1],
			/*ignore some browser(e.g. Maxthon,Iron,OmniWeb)*/
			safari: /webkit/.test( userAgent ) && !/chrome/.test( userAgent ),
			opera: /opera/.test( userAgent ),
			msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
			firefox: /firefox/.test( userAgent ) && !/opera/.test( userAgent ),
			chrome : /chrome/.test( userAgent ),
			mobile : /mobile/.test(userAgent),
			android : this.mobile && /android/.test(userAgent),
			ipod : this.mobile && /ipod/.test(userAgent),
			iphone : this.mobile && /iphone/.test(userAgent),
			ipad : this.mobile && /ipad/.test(userAgent)
		},
		getParam = function(param) {
			param = param.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
			var regexString = '[\\?&]' + param + '=([^&#]*)';
			var regex = new RegExp(regexString);
			var results = regex.exec(location);
			if (results == null) {
			  return '';
			} else {
			  return results[1];
			}
		},
		curry = function(f, n) {
			if (typeof(n) == 'undefined') n = f.length;
			var args = arguments;
			var _this = this;
			return function () {
		 		if (arguments.length >= n) {
					return f.apply(_this, arguments);
		 		} else {
					var a = [].slice.call(arguments,0);
					return args.callee.call(_this,function () {
						return f.apply(_this, a.concat([].slice.call(arguments,0)))
					},n - a.length);
				}
			};
		},
		each = function (o, f, args){
			if(Object.prototype.toString.apply(o) === '[object Array]')
				for ( var i = 0, ol = o.length , val = o[0];
					i < ol && f.apply(val, args ? args : [i,val]) !== false; val = o[++i] );
		   	else
				for ( var i in o ){
					var value =  f.apply( o[i], args ?  args : [i, o[i]]);
					if(value === false){
						break;
					}
				}
		},
		setCookie = function (name, value, options){
			var text = "";
			if(!!options){
				text += (options.expires instanceof Date ? ";expires=" + options.expires.toUTCString() : "") +
						(options.path ? ";path=" + options.path : "" ) +
						(options.domain ? ";domain=" + options.domain : "" ) +
						(options.secure ? ";secure" : "" );
			}			
			window.document.cookie =  encodeURIComponent(name) + "=" +
				encodeURIComponent( value ) +
				text;
		},
		getCookie = function(name) {
			var all_cookies = document.cookie.split(';'),
				temp_cookie = '',
				cookie_name = '',
				cookie_value = '',
				cookie_found = false;

			for (var i = 0, len = all_cookies.length; i < len; i++ ){
				temp_cookie = all_cookies[i].split( '=' );
				cookie_name = temp_cookie[0].replace(/^\s+|\s+$/g, '');
				if ( cookie_name == name ){
					cookie_found = true;
					if ( temp_cookie.length > 1 ){
						cookie_value = encodeURIComponent( temp_cookie[1].replace(/^\s+|\s+$/g, '') );
					}
					return cookie_value;
					break;
				}
				temp_cookie = null;
				cookie_name = '';
			}
			if ( !cookie_found ){
				return null;
			}
		},
		removeCookie =  function(name){
			setCookie(name,value,{expires:new Date(0)});
		},
		cache = [],
		tmpl = function tmpl(str, data){
			var fn = !/\W/.test(str) ? 
					cache[str] = cache[str] || tmpl($(str).innerHTML) :
					new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};" +
						"with(obj){p.push('" +
						str.replace(/[\r\t\n]/g, "")
						.split("<%").join("\t")
						.replace(/((^|%>)[^\t]*)'/g, "$1\r")
						.replace(/\t=(.*?)%>/g, "',$1,'")
						.split("\t").join("');")
						.split("%>").join("p.push('")
						.split("\r").join("\\'") +
						"');}return p.join('');"
					);
			return data ? fn( data ) : fn;
		},
		Linear ={
			easeIn: function (t, b, c, d) {
				return c*t/d + b;
			},
			easeOut: function (t, b, c, d) {
				return c*t/d + b;
			},
			easeInOut: function (t, b, c, d) {
				return c*t/d + b;
			}
		},
		Tasks = {
			_id: 0,
			_tasks: [],
			_dalay:25,
			start: function(){
				if (this._id )return;
				var self = this;
					(function(){
					  var t = self._tasks;
						for ( var i = 0; i < t.length; i++ )
							//remove the function from tasks
							if ( t[i]() === false ) {							
								t.splice(i, 1);
								i--;
							}
						self._id = setTimeout(arguments.callee, self._dalay );
					})();
			},
			stop: function(){
				clearTimeout( this._id);
				this._id = 0;
			},
			add: function(fn){
				this._tasks.push(fn);
				this.start();
			}
		},
		Dom = {};
		
	//simply do it
	each(["width",'height','offsetWidth','offsetHeight','offsetLeft','offsetTop'],function(i,v){
		Dom[v] = function(elem){
			return elem[v] || elem.style[v];
		}
	});
	
	function extend(d, s) {
		for (p in s) {
			d[p] = s[p];
		}
		return d;
	};
		
	function isDisplay(){
		return /^http[s]?:\/\/[^\/]*\/display\?/i.test(location);
	}

	function isReflow(){
		return getParam("reflow") === "true";
	};

	function isTest(){
		return getParam("performance") === "true";
	};
	
	function isMinifiedPage(){
		return !getParam("view");
	};
	
	function _getNode(node,dire,attr){
		var props = {
			"for": "htmlFor",
			"class": "className",
			readonly: "readOnly",
			maxlength: "maxLength",
			cellspacing: "cellSpacing",
			rowspan: "rowSpan",
			colspan: "colSpan",
			tabindex: "tabIndex"
		};
		if ( node.nodeType !== 1 && node.nodeType !== 9 ) {
			return null;
		}
		while((node = dire === "prev" ? node.previousSibling : 
							 dire === "next" ? node.nextSibling :
							 dire === "parent" ? node.parentNode :
							 null) 
				&& node.nodeType !== 1
				|| attr  && (new RegExp(attr.value,'i')).test(node.getAttribute(props[attr.name] || attr.name)))
				{
				}
		return !!node ? node : null;
	}
	
	function next(node, attr){
		return _getNode(node, 'next', attr);
	}
	
	function prev(node, attr){
		return _getNode(node, 'prev', attr);
	}
	
	function getElementsByClassName(container,name){
		 var elems = nodeToArray(container.getElementsByTagName('*')),
			result = [],
			reg = new RegExp('\s*' + name + '\s*');
		 each(elems,function(i, v){
			if(reg.test(v.className)){
				result.push(v);
			}
		 })
		 return result;
	}
	
	function containIn(container,ele){
		do{
			if(container == ele)
				return true;
		}while(ele = ele.parentNode)
		return false;
	}
	
	function arrayReduce(all, exclude){
		var val = [];
		for(var i = 0 , len = all.length; i < len ; i++){
			var found = false,elem = all[i];
			for(var m = 0 , length = exclude.length ; m < length; m++){
				if(exclude[m] === elem){
					found = true;
					break;
				}
			}
			if(!found){
				val.push(elem);
			}
		}
		return val;
	}
	function nodeToArray(nodes){
		var val = [];
		!isIE ? (val = [].slice.call(nodes)) : 
			(function(node){
				for(var i = 0 , len = nodes.length ; i < len ; i++){
					val.push(nodes[i]);
				}
			}(nodes));
		return val;
	}
	
	function showDetail(){
		insertCssText(dislayStyle);
		var doc = window.document,
			div = doc.createElement('div'),
			host = window.location.protocol + "//" + window.location.host,
			result = doc.createElement('div'),
			show = doc.createElement('div');
		//result.style.position = "fixed";
		result.style.top = "0";
		//result.style.zIndex='1000001';
		result.id="_sm_console_"
		show.style.position = "absolute";
		doc.body.appendChild(result);
		doc.body.appendChild(show);
		show.appendChild(doc.createTextNode('show'));	
		show.id="_sm_console_show_";

		jsonp( SERVER + '/display?key=' + getParam("key") + "&type=json" , function(json){
			result.innerHTML = tmpl(displayTmpl, json);
				var elem = $('_sm_console_'),
					pelem = nodeToArray(elem.getElementsByTagName('p')),
					move = Dom.offsetWidth(doc.body) - Dom.offsetWidth(elem),
					length = move / 2 + Dom.offsetWidth(elem),
					server = json.server,
					options = json.options;
				elem.style.left = (move / 2) + "px";
				each(pelem,function(i,v){
					var node = next(v);
					if(i != 0){
						 node.style.display = "none";
					}
					addEvent(v, "click", function(e){
						node && (style = node.style) && (node.e = style.display || 'block') && (style.display = node.e === 'none'  ? "block" : "none");
					});
				});
				addEvent($("_sm_console_hide_"),'click',function(e){
					var t = 0 , d = 100;
					Tasks.add(function(){
						 elem.style.left = Linear.easeIn(t,0,-(length),d) + "px";
						 if(t++ >= d ){
							show.style.display = "block";
							return false;
						}
					});	
				});
				addEvent(show,'click',function(e){
					var t = 0 , d = 100;
					setTimeout(function(){
						show.style.display = "none"; 
					},100);
					Tasks.add(function(){
						 elem.style.left = Linear.easeIn(t,-(length), length + (move /2) ,d) + "px";
						 if(t++ >= d ){	
							return false;			
						}
					});
				});
	
				addEvent($("_sm_console_options_"),'click',function(e){
					e.preventDefault();
					var href = arrayReduce(document.getElementsByTagName('a'),elem.getElementsByTagName('a') || []),
						option = '';
					each(options,function(i,v){
						option = option + (v ?  i + "=on&" : "");
					})
					each(href,function(i,v){
						if(!new RegExp("^" + server).test(v.href))
							v.href = server + "/htmlminify?" + (option + (option === '' ? '?' : '') + 'url=' + encodeURIComponent(v.href) );
					})
				});	

		});
	};
	
	
	function doReflow(callback){
		var test = 'testDisplay',
			r = new ReflowTimer(false);
		r.normalizeTimesToTest = false;
		r.tests = [test];
		r.onTestsComplete = function(medianReflowTimes) {
			var time = medianReflowTimes[test];
			var json =  isMinifiedPage() ? { 'mReflowTime':  time } : { 'oReflowTime': time };
			callback(json);
		};
		r.run();
	};
	
	function showPerformance(elem,json){
		extend(report,json);
		$(elem).innerHTML = tmpl(performanceTmpl,{result:report});
		if(!!$('reflow')){
			addEvent($('reflow'),'click',function(e){
				e.preventDefault();
				each(['original-frame','minified-frame'],function(i,v){
					$(v).contentWindow['_doReflow@Show:Minifier_'](function(json){
						window.parent.showPerformance('result',json);
					});
				});
			})
		}
	}
	
	function doPerformance(){
		if(!isTest())
			return;
		//var json = isMinifiedPage() ? "{ 'mTime' :"+  _minifier_performanceTime +"}" : "{ 'oTime':" +  _minifier_performanceTime + "}";
		//var time = window.name = json ;
		var serverTime = new Date(parseInt(getParam("sd"))),
			now = new Date,
			offset = now.getTime() - serverTime.getTime() > 0 ? now.getTime() - serverTime.getTime() :  serverTime.getTime() - now.getTime(),
			allTime;
			now.setTime(window['_startTime@Show:Minifier_']);
			allTime = now.getTime() - serverTime.getTime(),
			latency = now.getTime()-window['_pageSendTime@Show:Minifier_'],
			json = isMinifiedPage() ? { 'mTime' : window['_performanceTime@Show:Minifier_'] , 'mLatency' : latency } : { 'oTime':  window['_performanceTime@Show:Minifier_'] , 'oLatency': latency };
		try{
			window.parent.showPerformance('result',json);
		}catch(e){}	
		if(isReflow()){
			addEvent(window,'load',function(){
				doReflow(function(json){
					try{
						window.parent.showPerformance('result',json);
					}catch(e){}	
				});
			});
		}
	};
	
	function sendBeacon(){
			var uriParams = "key=" + getParam("key") + 
							"&browser=" + (browser.safari ? "safari" : 
											browser.opera ? "opera" :
											browser.msie ? "ie" :
											browser.firefox ? "firefox" :
											browser.chrome ? "chrome" :
											browser.android ? "android" :
											browser.ipod ? "ipod" :
											browser.iphone ? "iphone" :
											browser.ipad ? "ipad" :
											"other") +
							"&version=" + browser.version +
							"&minified=" + report.mTime + "&original=" + report.oTime +
							"&minifiedLatency=" + report.mLatency + "&originalLatency=" + report.oLatency +
							 (report.mReflowTime ? "&minifiedReflow=" + report.mReflowTime : "") +
							 (report.oReflowTime ? "&originalReflow=" + report.oReflowTime : "") ;
							//+"&latency=" + _minifier_startTime ;		
			var url = SERVER +"/beacon?" + uriParams + "&rd=" + radom;
			//Opera, Safira and Chrome have some problem with unload event that page sent request to server.
			if( /webkit/.test( userAgent )|| (/opera/.test( userAgent ) && !/msie/.test( userAgent ))){
				xhr = new XMLHttpRequest();
				xhr.open('GET',url,false);
				xhr.send(null);
				return;
			}
			(new Image).src= url;
			
	};
	//force opera unload event.
	if(/opera/.test( userAgent ) && !/msie/.test( userAgent )){
		opera.setOverrideHistoryNavigationMode('compatible');
		history.navigationMode = 'compatible';
	}
	if(isDisplay() && !isTest()){
		 showDetail();
	}else if(isDisplay() && isTest()){
		window['_doReflow@Show:Minifier_'] = doReflow;
		doPerformance();	
	}else{
		window.$ = $;
		window.addEvent = addEvent;
		window.urlReg = urlReg;
		window.each = each;
		window.getElementsByClassName = getElementsByClassName;
		window.getCookie = getCookie;
		window.setCookie = setCookie;
		window.removeCookie = removeCookie;
		window.nodeToArray = nodeToArray;
		window.showPerformance = showPerformance;
		window.sendBeacon = sendBeacon;
	}
	

	var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-16776685-1']);
  _gaq.push(['_trackPageview']);

  (function() {
	var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
	ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
	var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
	
}(window))
